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

суббота, 24 июля 2021 г.

Игра "Морской бой. След торпеды" Альфа-версия.

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


И вот подумалось, а почему бы не попробовать сделать то же самое в БГЕ. Пусть и мертв ныне этот движок, в смысле, не занимаются им больше создатели Блендера, но уж такое-то должен он потянуть.

Сказано- сделано. Альфа версия


Простенькая игра. "Морской бой. след торпеды" Создана в BGE, для работы игры испольуйте Blender 2.79b.Управление - перископ влево-вправо - стрелочки влево-вправо, выстрел - - пробел. При переходе на сцену подведения итогов - выход из игры - стрелочка вниз, новый круг - стрелочка вверх.
Длительность игрового сеанса - 3 минуты, боезапас - 20 торпед. После первого сеанса в разделе "Лучший результат будут нули, затем идет сравнение и результат улучшается в первую очередь по количеству побед за сеанс, во-вторых, если количество текущих побед совпало с лучшим результатом, идет сравнение с количеством израсходованного боезапаса, чем меньше израсходовано торпед на одну цель, тем лучше.

Можете поэкспериментировать с дальностью до генерируемых кораблей - открывайте скрипт Submarine_Control.py и ищите строчку почти в самом конце randomXY = random.randrange(200, 1400). В скобках эта самая дальность, можете расширить ее пределы, но не более 4500, хотя сомневаюсь, что на такой дистанции удастся вообще когда-нибудь попасть. А вот уменьшить, скажем до 800-1000 - улучшит количество попаданий. Отрицательные цифры ставить нельзя, и меньше 100 тоже.

Объем упакованного файла - 49 Мб, распакованного проекта - 74. Запускаемый файл - бленд SeaBattle>blend. Скрипт, о котором я говорил, находится в нем же.

Альфа версия. Только один тип мишени - незатекстуренный, тонет при попадании без всяких дополнительных эффектов, движение слева-направо. Предполагаю следующие изменения/дополнения:
1) Еще пара-тройка моделей кораблей.
2) Все модели с текстурами.
3) Смена текстуры неба случайным образом при старте (для разнообразия).
4) Возможность появления кораблей с другой стороны, но движение все равно будет только в одну сторону (типа конвой в море)
5) Если БГЕ не будет сильно возмущаться - добавить эффект разрушения - пожар на тонущем корабле с дымом.

Возможно, что при первом запуске будет "осекаться", у меня так бывало, при повторном запуске все нормально (может у меня просто другие программы мешали, их много было, запущенных). Возможно, будет нестандартное срабатывание - взрыв есть, но корабль не тонет, причем сработка происходит на самом "кончике" мишени, видимо, надо еще помудрить с настройками сенсора Неар. Но происходит это крайне редко - меньше, чем пальцев на руке при где-то сотне запусков и тестовых прогонах.

https://drive.google.com/file/d/1cfOjeJLvPIUFGBjxt1e19u2ubN6V5XBh/view?usp=sharing
https://yadi.sk/d/nqPDWcWfxCe93Q


Скрины:

Старт


Сама игра.

Подведение итогов



понедельник, 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 км.

понедельник, 2 апреля 2018 г.

Ведущий-ведомый. Эксперименты в коде.

Итак, задача. Провести перестроение пары ведущий-ведомый в БГЕ из строя "правый пеленг" в "левый пеленг". Задача решалась в БГЕ, объекты имели тип физики "но коллижн", то есть физика отсутствовала. Ведомый при получении команды немного "приотставал", разворачивался по отношению к ведущему под углом, после чего занимал свое место с противоположной стороны и, догнав "командира" уравнивал с ним скорость. Активно использовался метод getVectTo. В итоге, после опробования различных вариантов кода, получилось следующее:import bge
 
scene = bge.logic.getCurrentScene()
 
def Wingman():
    V_own = 0.0
    V_wingman = 0.0
    R_own_x = 0.0
    R_wingman_x = 0.0
    R_own_y = 0.0
    R_wingman_y = 0.0
    R_own_z = 0.0
    R_wingman_z = 0.0
 
 
    Wingman = scene.objects["MiG21Wingman"]
 
    cont = bge.logic.getCurrentController()
    own = cont.owner
    
    
    
    
    if own["prop"] == 0:
        V_own = 0.3
        V_wingman = 0.3
        
    
        
    #Перестройка в пеленг
    elif own["prop"] == 1:
        V_own = 0.3
        V_wingman = 0.3
        if own.getVectTo(Wingman)[2][0] > 0:
            V_own += 0.05
            V_wingman -= 0.05
            if abs(Wingman.getVectTo(own)[2][0]) < 0.5 and own.getVectTo(Wingman)[2][0] > 0:
                R_wingman_z += 0.01
            elif abs(Wingman.getVectTo(own)[2][0]) > 0.5 and own.getVectTo(Wingman)[2][0] < 0:
                R_wingman_z -= 0.01
            
        else:
            V_own = 0.3
            V_wingman = 0.3
            if Wingman.getVectTo(own)[2][0] + own.getVectTo(Wingman)[2][0] > 0:
                R_wingman_z -= 0.01
            else:
                R_wingman_z += 0.01
            
            if abs(Wingman.getVectTo(own)[2][0] + own.getVectTo(Wingman)[2][0]) > 0.01:
                V_own -= 0.05
                V_wingman += 0.05
            else:
                if Wingman.worldOrientation[2] != own.worldOrientation[2]:
                    V_own = 0.3
                    V_wingman = 0.3
                    Wingman.worldOrientation[2] = own.worldOrientation[2]
                else:
                    if own.getDistanceTo(Wingman) > V_own*60:
                        V_own = 0.3
                        V_wingman += 0.1 
                    else:
                        own["prop"] == 0
                        V_own = 0.3
                        V_wingman = 0.3 
            
    
    elif own["prop"] == 2:
        V_own = 0.3
        V_wingman = 0.3
        if own.getVectTo(Wingman)[2][0] < 0:
            V_own += 0.05
            V_wingman -= 0.05
            if abs(Wingman.getVectTo(own)[2][0] > 0.5) and own.getVectTo(Wingman)[2][0] > 0:
                R_wingman_z += 0.01
            elif abs(Wingman.getVectTo(own)[2][0] < 0.5) and own.getVectTo(Wingman)[2][0] < 0:
                R_wingman_z -= 0.01
            
        else:
            V_own = 0.3
            V_wingman = 0.3
            if Wingman.getVectTo(own)[2][0] + own.getVectTo(Wingman)[2][0] < 0:
                R_wingman_z += 0.01
            else:
                R_wingman_z -= 0.01
            
            if abs(Wingman.getVectTo(own)[2][0] + own.getVectTo(Wingman)[2][0]) > 0.01:
                V_own -= 0.05
                V_wingman += 0.05
            else:
                if Wingman.worldOrientation[2] != own.worldOrientation[2]:
                    V_own = 0.3
                    V_wingman = 0.3
                    Wingman.worldOrientation[2] = own.worldOrientation[2]
                else:
                    if own.getDistanceTo(Wingman) > V_own*60:
                        V_own = 0.3
                        V_wingman += 0.1 
                    else:
                        own["prop"] == 0
                        V_own = 0.3
                        V_wingman = 0.3 
            
                       
    own.applyMovement([0.0, V_own, 0.0], True)
    Wingman.applyMovement([0.0, V_wingman, 0.0], True)
    own.applyRotation([R_own_x, R_own_y, R_own_z], True)
    Wingman.applyRotation([R_wingman_x, R_wingman_y, R_wingman_z], True)
 
Параллельно с написанием пробного скрипта перестроения пары самолетов пришлось составлять таблийу для значений векторов метода getVectTo[2] . Если попытаться представить это в трехмерном пространстве, то получается куб, в центра которого находится ведущий, а в углах, центрах граней и ребер - ведомый. Условно говоря, конечно. То есть ведомый может идти параллельно ведущему рядом с ним. Или с отставанием справа и сверху или с опережением слева по центру и так далее. В итоге табличку я сюда солью, может потом пригодиться (хотя бы и самому себе).
 
НА ОДНОМ УРОВНЕ
Спереди по центру [0.0, 1.0, 0.0]
Сзади по центру [0.0, -1.0, 0.0]
Слева спереди [-0.7071, 0.7071, 0.0]
Справа спереди [0.7071, 0.7071, 0.0]
Слева сзади [-0.7071, -0.7071, 0.0]
Справа сзади [0.7071, -0.7071, 0.0]
Справа по центру [1.0, 0.0, 0.0]
Слева по центру [-1.0, 0.0, 0.0]
 
ВЫШЕ
Выше по центру [0.0, 0.0, 1.0]
Спереди по центру [0.0, 0.7071, 0.7071]
Сзади по центру [0.0, -0.7071, 0.7071]
Слева спереди [-0.5774, 0.5774, 0.5774]
Справа спереди [0.5774, 0.5774, 0.5774]
Слева сзади [-0.5774, -0.5774, 0.5774]
Справа сзади [0.5774, -0.5774, 0.5774]
Справа по центру [0.7071, 0.0, 0.7071]
Слева по центру [-0.7071, 0.0, 0.7071]
 
НИЖЕ
Ниже по центру [0.0, 0.0, -1.0]
Спереди по центру [0.0, 0.7071, -0.7071]
Сзади по центру [0.0, -0.7071, -0.7071]
Слева спереди [-0.5774, 0.5774, -0.5774]
Справа спереди [0.5774, 0.5774, -0.5774]
Слева сзади [-0.5774, -0.5774, -0.5774]
Справа сзади [0.5774, -0.5774, -0.5774]
Справа по центру [0.7071, 0.0, -0.7071]
Слева по центру [-0.7071, 0.0, -0.7071]
 
Потом, когда все это было закончено, я попробовал сделать немного по-другому. А именно - сделать так, чтобы ведущий постоянно вычислял координаты ведомого относительно себя, любимого, чтобы подчиненный просто занимал эту область, не заморачиваясь с векторами. Тем более, что гораздо раньше у меня уже был отработан метод наведения ботов на цель с помощью крена, тангажа или рыска по отдельности или сразу всего одновременно. Никаких проблем с наведением ведомого на эту самую искомую точку быть не должно. Однако вот тут я застрял. Как поется в одной старой песне из кинофильма: "Уж я к ней и так и этак, со словами и без слов. Обломал немало веток, наломал немало дров". В итоге все же удалось решить проблему со строем "фронт" - когда ведомый и ведущий выстроены в одну линию параллельно друг другу и строем "колонна", когда ведомый держится строго позади ведущего. А вот со строем "пеленг" пока облом. Там нужно реализовать отставание ведомого от ведущего, либо его опережение.  Ну, хоть что-то получилось, а с пеленгом я все равно как-нибудь разберусь. Код написан уже для объектов с физикой типа Dynamics.
 
port bge
import math
scene = bge.logic.getCurrentScene()
 
def Wingman():
    cont = bge.logic.getCurrentController()
    own = cont.owner
    #Задание объекта-цели
    target = scene.objects["wingman"]
    target.worldOrientation = own.worldOrientation
    #target.worldPosition = front(own, target)
    target.worldPosition = peleng(own, target)
    #target.worldPosition =colonna(own, target)
 
def colonna(own, target):
    targetX = own.worldPosition[0] - 3*own.worldLinearVelocity[0]
    targetY = own.worldPosition[1] - 3*own.worldLinearVelocity[1]
    targetZ = own.worldPosition[2] - 3*own.worldLinearVelocity[2]
    listCoord = [targetX, targetY, targetZ]
    return listCoord
    
def front(own, target):
    LeftRightX = 0
    LeftRightY = 0
    
    kY = own.worldLinearVelocity[0]/own.localLinearVelocity[1]
    kX = own.worldLinearVelocity[1]/own.localLinearVelocity[1]
    kZ = own.worldLinearVelocity[2]/own.localLinearVelocity[1]
    
    PRAVOLEVO = 1
    
    if kX > 0 and kY > 0:
        LeftRightX = 1
        LeftRightY = -1
    elif kX < 0 and kY > 0:
        LeftRightX = 1
        LeftRightY = -1
    elif kX > 0 and kY < 0:
        LeftRightX = 1
        LeftRightY = -1
    elif kX < 0 and kY < 0:
        LeftRightX = 1
        LeftRightY = -1
    
    ownX = own.worldPosition[0]
    ownY = own.worldPosition[1]
    ownZ = own.worldPosition[2]
    
    DIST = 20.0
    
    targetX = ownX + DIST*kX*LeftRightX*PRAVOLEVO
    targetY = ownY + DIST*kY*LeftRightY*PRAVOLEVO
    targetZ = ownZ + DIST*kZ
    
    listCoord = [targetX, targetY, targetZ]
    return listCoord
 
В функциях colonna и front  идет как раз вычисление и выдача искомых координат ведомого. Комментариев в коде почти нет, потому как работа над этим делом еще не закончена. Получится ли из этого что-нибудь, пока неясно. Во всяком случае, в основном коде проекта включить их можно хоть сейчас, но хотелось бы добить строй "пеленг"...